
from view.endSrc.MySqlConn import MySqlConn
from view.endSrc.MyTools import MyTools

import numpy as np

import datetime as dt

class tIniceRunner:

    def setSqlConn(self, dbConn: MySqlConn):
        '''
            let use set sql connector dynamatically
        :param dbConn:
        :return:
        '''
        if dbConn is not None:
            self._dbconn = dbConn
            self._logger = dbConn.logger

    def __init__(self, dbconn=None):
        self._dbconn = None
        self._logger = None
        self.setSqlConn(dbconn)

        self.id = None
        self.datasetId = None           # run the algorithm on which dataset
        self.name = None                # name of the algorithm
        self.startTime = None
        self.endTime = None
        self.observerGenClassName = None      # ObserverGeneratorClassName
        self.nObservers = None
        self.centers = None             # JSON dict
        self.nCenters = None

        self.ARI = None                 # clustering index
        self.purity = None              # clustering index
        self.NMI = None                 # clustering index


    def __str__(self):
        '''
            for simple test
        :return:
        '''
        tmp = 'id: ' + str(self.id) + '\n'
        tmp += 'datasetId: ' + str(self.datasetId) + '\n'
        tmp += 'name: ' + str(self.name) + '\n'
        tmp += 'startTime: ' + str(self.startTime) + '\n'
        tmp += 'endTime: ' + str(self.endTime) + '\n'
        tmp += 'observerGenClassName: ' + str(self.observerGenClassName) + '\n'
        tmp += 'nObservers: ' + str(self.nObservers) + '\n'
        tmp += 'centers: ' + str(self.centers) + '\n'
        tmp += 'nCenters: ' + str(self.nCenters) + '\n'

        tmp += 'ARI: ' + str(self.ARI) + '\n'
        tmp += 'purity: ' + str(self.purity) + '\n'
        tmp += 'NMI: ' + str(self.NMI) + '\n'

        return tmp

    def createTable(self):

        assert self._dbconn is not None, 'please set setSqlConn first'

        if self._dbconn.tableExists('tIniceRunner'):
            print('exist..tIniceRunner, if you want to drop it, please call MySqlConn.dropTable')
            return True

        sql = """create table tIniceRunner(
                id int primary key auto_increment not null,
                datasetId int not null,
                name CHAR(200) not null,
                startTime DATETIME,
                endTime DATETIME,
                observerGenClassName CHAR(200) not null,
                nObservers int not null,
                centers JSON,
                nCenters int,
                ARI double,
                purity double,
                NMI double ) """

        # foreign key(sessionId) references tIniceRuns(id)), do not use it for development

        if not self._dbconn.createTable(sql):
            self._logger.write('tIniceRunner.createTable is failed')
            return False
        return True


    def createRow(self, datasetId: int, name: str, observerGenClassName: str, nObservers: int):

        assert self._dbconn is not None, 'please set setSqlConn first'

        assert isinstance(name, str) and isinstance(observerGenClassName, str)
        assert len(name) > 0 and len(observerGenClassName) > 0
        assert isinstance(nObservers, int) and nObservers > 0

        rtup = (datasetId,
                name,
                observerGenClassName,
                nObservers)
        sql = "INSERT INTO tIniceRunner( " \
                " datasetId, name, " \
                " observerGenClassName, nObservers)" \
                "VALUES ( %d, '%s', " \
                        " '%s', %d ) " % tuple(rtup)

        newId = self._dbconn.insertRetId(sql)
        if newId is not None:
            self.id = newId
            self.datasetId = datasetId
            self.name = name
            self.observerGenClassName = observerGenClassName
            self.nObservers = nObservers

        else:
            self._logger.write('tObserver.createRow: failed to insert..' + str(rtup))
        return newId


    def getOldRunConfigNameList(self):

        sql = "SELECT id, name FROM tIniceRunner"
        rows = self._dbconn.read(sql)
        if rows is None:
            self._logger.write('tIniceRunner.getOldRunConfigNameList..failed to read records')
            return None
        if len(rows) == 0:
            self._logger.write('tIniceRunner.getOldRunConfigNameList.. read nothing')
        return rows


    def readRow(self, id):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert isinstance(id, int) and id > 0

        #   id int primary key auto_increment not null,
        #   datasetId int not null,
        #   name CHAR(200) not null,
        #   startTime DATETIME,
        #   endTime DATETIME,
        #   observerGenClassName CHAR(200) not null,
        #   nObservers int not null,
        #   centers JSON,
        #   nCenters int,
        #   ARI double,
        #   purity double,
        #   NMI double
        sql = "SELECT * FROM tIniceRunner WHERE id = %d" % id
        rows = self._dbconn.read(sql)
        if rows is None or len(rows) == 0:
            self._logger.write('tIniceRunner.readRow..failed to read by id= ' + str(id))
            return False
        row = rows[0]

        self.id, self.datasetId, self.name, self.startTime, self.endTime, \
        self.observerGenClassName, self.nObservers,self.centers, self.nCenters, \
        self.ARI, self.purity, self.NMI = row

        return True

    def updateStartTimeByCurrent(self):

        #   id int primary key auto_increment not null,
        #   datasetId int not null,
        #   name CHAR(200) not null,
        #   startTime DATETIME,
        #   endTime DATETIME,
        #   observerGenClassName CHAR(200) not null,
        #   nObservers int not null,
        #   centers JSON,
        #   nCenters int,
        #   ARI double,
        #   purity double,
        #   NMI double
        assert self._dbconn is not None, 'please set setSqlConn first'
        assert self.id is not None and isinstance(self.id, int) and self.id > 0

        cur = dt.datetime.strptime(str(dt.datetime.now()), "%Y-%m-%d %H:%M:%S.%f")
        rtup = (cur, self.id)
        sql = "UPDATE tIniceRunner SET startTime='%s' " \
               " WHERE id=%d " % rtup
        if not self._dbconn.insDelUpd(sql):
            self._logger.write('tIniceRunner.updateStartTimeByCurrent DB updating is failed for id= ' + str(self.id))
            return False
        return True

    def updateEndTimeByCurrent(self):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert self.id is not None and isinstance(self.id, int) and self.id > 0

        cur = dt.datetime.strptime(str(dt.datetime.now()), "%Y-%m-%d %H:%M:%S.%f")
        rtup = (cur, self.id)
        sql = "UPDATE tIniceRunner SET endTime='%s' " \
              " WHERE id=%d " % rtup
        if not self._dbconn.insDelUpd(sql):
            self._logger.write('tIniceRunner.updateEndTimeByCurrent DB updating is failed for id= ' + str(self.id))
            return False
        return True


    def updateCenters(self, centers: list):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert self.id is not None and isinstance(self.id, int) and self.id > 0

        assert isinstance(centers, list)

        shp = np.shape(centers[0])
        MyTools.checkMemberShape(centers, shp, 'tIniceRunner.updateCenters.centers')

        #   id int primary key auto_increment not null,
        #   datasetId int not null,
        #   name CHAR(200) not null,
        #   startTime DATETIME,
        #   endTime DATETIME,
        #   observerGenClassName CHAR(200) not null,
        #   nObservers int not null,
        #   centers JSON,
        #   nCenters int,
        #   ARI double,
        #   purity double,
        #   NMI double

        centerList = centers
        if shp != ():
            centerList = []
            for c in centers:
                centerList.append(list(c))

        ctsDict = {'cts': centerList}
        ctsJSON = MyTools.getJsonByDict(ctsDict)

        # write to DB
        rtup = (len(centerList),
                ctsJSON,
                self.id)
        # create sql
        sql = "UPDATE tIniceRunner SET " \
              "nCenters = %d, centers = '%s'" \
              "WHERE id= %d " % rtup

        if not self._dbconn.insDelUpd(sql):
            self._logger.write('tIniceRunner.updateCenters DB updating is failed for id= ' + self.id)
            return False

        # set memory fields at last
        self.centers = centers
        self.nCenters = len(centers)
        return True

    def updateIndex(self, indexName, value):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert self.id > 0, 'must have a valid DB id'

        assert indexName == 'ARI' or indexName == 'NMI' or \
            indexName == 'purity', 'only these indices are supported'
        assert value is not None

        value = float(value)

        sql = "UPDATE tIniceRunner SET "
        sql += indexName + "=" + str(value)
        sql += " WHERE id=" + str(self.id)

        if not self._dbconn.insDelUpd(sql):
            self._logger.write('tIniceRunner.updateIndex DB updating is failed for id= ' + self.id)
            return False

        setattr(self, indexName, value)
        return True


    def updateIndices(self, ARI, purity, NMI):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert self.id > 0, 'must have a valid DB id'

        #
        #   id int primary key auto_increment not null,
        #   datasetId int not null,
        #   name CHAR(200) not null,
        #   startTime DATETIME,
        #   endTime DATETIME,
        #   observerGenClassName CHAR(200) not null,
        #   nObservers int not null,
        #   centers JSON,
        #   nCenters int,
        #   ARI double,
        #   purity double,
        #   NMI double

        # write to DB
        rtup = (ARI,
                purity,
                NMI,
                self.id)
        # create sql
        sql = "UPDATE tIniceRunner SET " \
              "ARI = %f, purity = %f, NMI=%f " \
              "WHERE id= %d " % rtup

        if not self._dbconn.insDelUpd(sql):
            self._logger.write('tIniceRunner.updateIndices DB updating is failed for id= ' + self.id)
            return False

        self.ARI = ARI
        self.purity = purity
        self.NMI = NMI

        return True